package com.zxjbyte.yiyi.framework.common.core.okhttp.retry;

import com.github.rholder.retry.*;
import com.zxjbyte.yiyi.framework.common.core.okhttp.OkResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * 重试拦截器
 * 基于guava-retrying 实现重试策略
 * @Author zhangxingjia
 * @Date 2024/3/16 12:20
 * @Version: 1.0
 */
@Slf4j
@RequiredArgsConstructor
public class RetryInterceptor implements Interceptor {

    private final RetryPolicy retryPolicy;

    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request();
        boolean hasIfResult = Objects.nonNull(retryPolicy.getResultPredicate());
        Retryer<Response> retryer = buildRetry(retryPolicy);
        // 初始化引用为null，用于保存最后一次尝试的响应
        final Response[] lastAttemptResponse = {null};
        Callable<Response> callable = () -> {
            try {
                // Attempt to execute the request
                Response response = chain.proceed(request);
                lastAttemptResponse[0] = response;
                if(hasIfResult){
                    ResponseBody body = response.peekBody(Long.MAX_VALUE);
                    return response.newBuilder().body(body).build();
                }
                return response;
            } catch (IOException e) {
                log.warn("Request failed, will retry...");
                throw e;
            }
        };
        try {
            Response callResponse = retryer.call(callable);
            if(hasIfResult){
                return lastAttemptResponse[0];
            }
            return callResponse;
        } catch (ExecutionException e) {
            // 这个异常会在被调用的任务内部抛出一个异常时被抛出。
            if(Boolean.TRUE.equals(retryPolicy.getThrowOnExecutionFailure())){
                throw new IOException("Exception during request execution", e.getCause());
            }else{
                log.error("Retry call >>>> Exception during request execution " + e.getCause());
            }
        } catch (RetryException e) {
            // 这个异常会在重试器尝试了预设的最大重试次数之后，如果任务仍然没有成功，
            // 则抛出。RetryException表明重试已经完成，但是没有成功地得到一个结果。
            // 这种情况通常发生在任务持续失败（比如，由于持续的网络问题或外部服务不可用）并且已经达到了重试策略定义的重试次数上限。
            if(Boolean.TRUE.equals(retryPolicy.getThrowOnRetryFailure())){
                throw new IOException("Failed to execute request after retries", e);
            } else {
                log.error("Retry call >>>> Failed to execute request after retries", e);
            }
        }
        if (lastAttemptResponse[0] != null) {
            // 如果存在，返回最后一次尝试的响应
            return lastAttemptResponse[0];
        } else {
            // 如果不存在（理论上不应该发生），返回一个错误响应
            return new Response.Builder()
                    .request(chain.request())
                    .protocol(Protocol.HTTP_1_1)
                    .code(504) // Gateway Timeout
                    .message("Unable to complete request after retries")
                    .body(ResponseBody.create("{\"status\":\"error\",\"message\":\"No response received\"}", MediaType.parse("application/json; charset=utf-8")))
                    .build();
        }
    }

    private static Retryer<Response> buildRetry(RetryPolicy retryPolicy) {
        RetryerBuilder<Response> builder = RetryerBuilder.newBuilder();
        builder.retryIfExceptionOfType(retryPolicy.getExceptionClass());
        builder.retryIfRuntimeException();
        builder.withWaitStrategy(WaitStrategies.fixedWait(retryPolicy.getSleepTime(), TimeUnit.SECONDS));
        builder.withStopStrategy(StopStrategies.stopAfterAttempt(retryPolicy.getAttemptNumber()));
        if(Objects.nonNull(retryPolicy.getResultPredicate())){
            builder.retryIfResult(result -> retryPolicy.getResultPredicate().apply(new OkResponse(result)));
        }
        if(Objects.nonNull(retryPolicy.getDuration())){
            builder.withAttemptTimeLimiter(new RequestAttemptTimeLimiter<>(retryPolicy.getDuration(), TimeUnit.SECONDS));
        }
        if(Objects.nonNull(retryPolicy.getListener())){
            builder.withRetryListener(retryPolicy.getListener());
        }
        return builder.build();
    }

}
